# 网络服务 https
30 秒速记
HTTPS=HTTP跑在TLS上:内容加密 + 验证对方身份- 客户端验证证书看三件事:信任链能追到受信任的
CA、没过期、域名和证书的SAN对得上 - 自签名、内网
CA证书的正确姿势是通过ca选项或NODE_EXTRA_CA_CERTS加信任,不是关校验 rejectUnauthorized: false/NODE_TLS_REJECT_UNAUTHORIZED=0等于没有身份验证,只能临时排查用- 私钥不进仓库、不进镜像层,走密钥管理;线上常在
Nginx或网关终止TLS
Node 的 https 模块就是在 http 外面套了一层 TLS,用法几乎一样,多出来的全是证书相关的事。 TLS 干两件事:一是把内容加密,二是让客户端确认「对面真的是这个网站」,后者靠证书。所以 HTTPS 出问题,十有八九是证书问题:证书链不全、过期、域名对不上、用了自签名。碰到证书报错,正确做法是把对应的 CA 加进信任列表,而不是把校验关掉,关掉就等于谁都能冒充服务端。
理解「网络服务 https」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
HTTPS 客户端和服务端在 Node 里的写法跟 http 基本是一一对应的,差别都在证书相关的选项上。
const https = require('https')
const fs = require('fs')
// 服务端:比 http.createServer 多一个 { key, cert }
https
.createServer(
{ key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') },
(req, res) => res.end('hello https'),
)
.listen(8443)
// 客户端:请求自签名证书的服务
https.get(
'https://localhost:8443',
{ ca: fs.readFileSync('cert.pem') }, // 把这张证书当成受信任的 CA,校验照常进行
(res) => res.pipe(process.stdout), // 输出 hello https
).on('error', (e) => console.error(e.code)) // 不传 ca 时输出 DEPTH_ZERO_SELF_SIGNED_CERT
不传 ca 时,客户端会报 DEPTH_ZERO_SELF_SIGNED_CERT,这正是证书校验在起作用。很多教程教的 rejectUnauthorized: false 能让请求通过,但它把「验证对方是谁」整个关掉了,只适合在本机临时确认是不是证书问题。
常见报错和对应原因可以记一下:DEPTH_ZERO_SELF_SIGNED_CERT / SELF_SIGNED_CERT_IN_CHAIN 是自签名或私有 CA,加信任;UNABLE_TO_VERIFY_LEAF_SIGNATURE 一般是服务端没发中间证书;CERT_HAS_EXPIRED 是过期;ERR_TLS_CERT_ALTNAME_INVALID 是访问的域名不在证书的 SAN 列表里。
面试官追问
追问 1Node 请求内网服务报 self signed certificate,同事加了 NODE_TLS_REJECT_UNAUTHORIZED=0 就好了,能这么上线吗?
我会直接拦。这个环境变量会让整个进程所有 HTTPS 请求都不验证证书,中间人可以随便冒充。正确做法是拿到内网 CA 证书,启动时设 NODE_EXTRA_CA_CERTS=/path/ca.pem,或者在请求里传 ca 选项。
追问 2浏览器能打开的网站,Node 请求却报 unable to verify the first certificate,为什么?
多半是服务端没把中间证书发全。浏览器会自动补全或者有缓存,Node 不会。用 openssl s_client -connect host:443 -showcerts 看一下发回来的证书链,让服务端配上完整链才是根治。
追问 3证书过期导致线上请求全挂,怎么提前发现?
定时脚本读证书的到期时间,比如 tls.connect 之后 socket.getPeerCertificate().valid_to,剩 30 天就告警。证书用 Let's Encrypt 这类自动续期的话,也要监控续期任务有没有真的跑成功。
追问 4Node 服务要自己开 HTTPS,还是交给前面的 Nginx?
一般交给 Nginx 或网关,证书集中管、续期方便,Node 只跑 HTTP。只有内网也要求加密、或者没有反向代理的时候,才让 Node 自己加载证书。
# 模块概览
30 秒速记
https模块API和http几乎一一对应:https.get/https.request/https.createServer- 多出来的是
TLS选项:服务端要key、cert,客户端可以传ca、rejectUnauthorized、servername - 浏览器和
Node的信任库不一样:Node用自带的CA列表,不读系统证书(Node 23.8起才可以用--use-system-ca) - 访问证书不受信任的站点,正确做法是加
ca,rejectUnauthorized: false只能临时排查
https 模块的用法跟 http 几乎一样,把模块名和协议换掉就能跑,真正要多学的是证书。 服务端启动时要提供私钥和证书;客户端默认会严格校验对方证书,校验不过直接报错。早年 12306 用的是自己签的证书,浏览器要手动装根证书,Node 去请求也会报错,就是这个原因。还有一点容易忽略:Node 不用操作系统的证书库,公司内网 CA 装到系统里了,浏览器能打开,Node 照样报错。
这个模块的重要性,基本不用强调了。在网络安全问题日益严峻的今天,网站采用HTTPS是个必然的趋势。
在nodejs中,提供了 https 这个模块来完成 HTTPS 相关功能。从官方文档来看,跟 http 模块用法非常相似。
本文主要包含两部分:
- 通过客户端、服务端的例子,对https模块进行入门讲解。
- 如何访问安全证书不受信任的网站。(以 12306 为例子)
篇幅所限,本文无法对 HTTPS协议 及 相关技术体系 做过多讲解,有问题欢迎留言交流。
面试官追问
追问 1把 require('http') 换成 require('https'),服务就能上线了吗?
不能。服务端 createServer 第一个参数要传 { key, cert },没有证书根本起不来。客户端那边,对方证书有问题也会直接报错,得先把证书链路理顺。
追问 2公司内网 CA 已经装进系统了,浏览器能打开,Node 还是报证书错误,为什么?
Node 默认用自己打包的 CA 列表,不读系统证书库。启动时加 NODE_EXTRA_CA_CERTS=/path/company-ca.pem 就行,新版本也可以用 --use-system-ca。
追问 3联调时测试环境证书不受信任,想临时绕过,怎么做才不出事?
只在这一个请求上传 ca 指向测试环境的证书,校验照常进行。实在要用 rejectUnauthorized: false,也只写在测试代码里,别用全局环境变量,更别带进生产配置。
追问 4一台服务器上挂了多个域名的 HTTPS 服务,客户端拿到的证书不对,可能是什么?
SNI 没传对。Node 客户端默认用 hostname 当 servername,如果是用 IP 直连再手动加 Host 头,就要显式传 servername: 'api.example.com',服务端才知道该返回哪张证书。
# 客户端例子
30 秒速记
https.get(url, cb)和http.get写法一样,回调拿到的res也是流res.on('data')里拿到的是一块块数据,要完整内容就拼起来等end.on('error')管的是请求侧失败:DNS、连接、证书校验;正文读到一半断开要看res的close/aborted- 证书错误会走这里的
error,比如CERT_HAS_EXPIRED,不监听进程直接崩 - 要设方法、请求头或写正文,就换
https.request
https.get() 跟 http.get() 的用法完全一样,只是请求地址换成了 https。 回调触发时只有状态码和响应头到了,正文通过 data 一块块来,示例里直接 process.stdout.write 边收边打。.on('error') 一定要写,HTTPS 比 HTTP 多了一类错误就是证书校验失败,过期、域名不匹配、自签名都会从这里抛出来。要自定义方法、请求头或者带正文,就换 https.request(options, cb),最后记得 end()。
